#include "driver.h"

#ifdef WIN32
#include <windows.h>

#ifndef UNICODE
# define DISPLAY_DEVICE       DISPLAY_DEVICEA
# define PDISPLAY_DEVICE      PDISPLAY_DEVICEA
# define ENUM_DISPLAY_DEVICES "EnumDisplayDevicesA"
#else
# define DISPLAY_DEVICE       DISPLAY_DEVICEW
# define PDISPLAY_DEVICE      PDISPLAY_DEVICEW
# define ENUM_DISPLAY_DEVICES "EnumDisplayDevicesW"
#endif

typedef WINUSERAPI BOOL (WINAPI* PFN_ENUM_DISPLAY_DEVICES) (LPCTSTR         lpDevice,
                                                            DWORD           iDevNum,
                                                            PDISPLAY_DEVICE lpDisplayDevice,
                                                            DWORD           dwFlags);

BOOL
Epsilon_EnumDisplayDevices (LPCTSTR         lpDevice,
                            DWORD           iDevNum,
                            PDISPLAY_DEVICE lpDisplayDevice,
                            DWORD           dwFlags)
{
  static PFN_ENUM_DISPLAY_DEVICES EnumDisplayDevicesW32 = NULL;

  if (! EnumDisplayDevicesW32) {
    HINSTANCE hInstUser32 = NULL;
              hInstUser32 = LoadLibrary (_T ("User32.DLL"));

    if (! hInstUser32)
      return FALSE;

    // Get the address of the EnumDisplayDevices function
    EnumDisplayDevicesW32 = (PFN_ENUM_DISPLAY_DEVICES)GetProcAddress (hInstUser32, ENUM_DISPLAY_DEVICES);

    FreeLibrary (hInstUser32);

    if (! EnumDisplayDevicesW32)
      return FALSE;
  }

  return EnumDisplayDevicesW32 (lpDevice, iDevNum, lpDisplayDevice, dwFlags);
}
#endif

bool
E3D_Driver_Init (void)
{
#ifdef WIN32
  return true;
#else
  #ifndef __APPLE__
    // TODO: Linux
    return false;
  #else
    return true;
  #endif
#endif
}

e3dDisplayDevices
e3dDriver::GetDevices (void)
{
  e3dDisplayDevices devices;

#ifdef WIN32
  int            DeviceNum = 0;
  DISPLAY_DEVICE DisplayDevice;

  for (;;) {
    ZeroMemory (&DisplayDevice, sizeof (DisplayDevice));
    DisplayDevice.cb = sizeof (DisplayDevice); 

    if (Epsilon_EnumDisplayDevices (NULL, DeviceNum, &DisplayDevice, NULL)) {
      e3dDisplayDevice device;

      device.setDeviceID    ((TCHAR *)DisplayDevice.DeviceName);
      device.setDeviceName  ((TCHAR *)DisplayDevice.DeviceString);
      
      Epsilon_EnumDisplayDevices (device.getDeviceID (), 0, &DisplayDevice, NULL);

      device.setMonitorName ((TCHAR *)DisplayDevice.DeviceString);

      devices.push_back (device);

      DeviceNum++;
    } else {
      return devices;
    }
  }
#endif

  return devices;
}

#include "OpenGL.h"

/* GL_ARB_imaging enumerants */
#ifndef GL_CONSTANT_COLOR
# define GL_CONSTANT_COLOR                 0x8001
# define GL_ONE_MINUS_CONSTANT_COLOR       0x8002
# define GL_CONSTANT_ALPHA                 0x8003
# define GL_ONE_MINUS_CONSTANT_ALPHA       0x8004
# define GL_BLEND_COLOR                    0x8005
#endif


using namespace e3dRenderStates;

void
e3dRenderState::SetAlphaFunc (TestFunction func, float ref)
{
  if (AlphaFunction != func || AlphaRefVal != ref) {
    switch (func) {
      case Less:
        glAlphaFunc (GL_LESS, ref);
        break;
      case LessOrEqual:
        glAlphaFunc (GL_LEQUAL, ref);
        break;
      case Greater:
        glAlphaFunc (GL_GREATER, ref);
        break;
      case GreaterOrEqual:
        glAlphaFunc (GL_GEQUAL, ref);
        break;
      case Equal:
        glAlphaFunc (GL_EQUAL, ref);
        break;
      case NotEqual:
        glAlphaFunc (GL_NOTEQUAL, ref);
        break;
      case Always:
        glAlphaFunc (GL_ALWAYS, ref);
        break;
      case Never:
      default:
        glAlphaFunc (GL_NEVER, ref);
        break;
    }

    AlphaFunction = func;
    AlphaRefVal   = ref;
  }
}

void
e3dRenderState::SetAlphaTest (e3dRenderStates::Boolean test)
{
  if (AlphaTest != test) {
    if (test == True) {
      glEnable (GL_ALPHA_TEST);
    } else {
      glDisable (GL_ALPHA_TEST);
    }

    AlphaTest = test;
  }
}

GLint
EpsilonGL_EnumBlendOp (e3dRenderStates::BlendOp op)
{
  switch (op) {
    case Zero:
      return GL_ZERO;
    case One:
      return GL_ONE;
    case DestColor:
      return GL_DST_COLOR;
    case InverseDestColor:
      return GL_ONE_MINUS_DST_COLOR;
    case SourceAlpha:
      return GL_SRC_ALPHA;
    case InverseSourceAlpha:
      return GL_ONE_MINUS_SRC_ALPHA;
    case DestAlpha:
      return GL_DST_ALPHA;
    case InverseDestAlpha:
      return GL_ONE_MINUS_DST_ALPHA;
    case SourceAlphaSaturate:
      return GL_SRC_ALPHA_SATURATE;

    /* GL_ARB_imaging */
    case ConstantColor:
      return GL_CONSTANT_COLOR;
    case InverseConstantColor:
      return GL_ONE_MINUS_CONSTANT_COLOR;
    case ConstantAlpha:
      return GL_CONSTANT_ALPHA;
    case InverseConstantAlpha:
      return GL_ONE_MINUS_CONSTANT_ALPHA;

    /* Dest. Op Only */
    case SourceColor:
      return GL_SRC_COLOR;
    case InverseSourceColor:
      return GL_ONE_MINUS_SRC_COLOR;

    default:
      return GL_INVALID_ENUM;
  }
}

void
e3dRenderState::SetBlend (e3dRenderStates::Boolean blend)
{
  if (Blend != blend) {
    if (blend == True)
      glEnable  (GL_BLEND);
    else
      glDisable (GL_BLEND);

    Blend = blend;
  }
}

void
e3dRenderState::SetBlendFunc (e3dRenderStates::BlendOp source,
                              e3dRenderStates::BlendOp dest)
{
  if (BlendSourceOp != source || BlendDestOp != dest) {
    /* TODO: ValidateOp - Some Operations do not work for both Src and Dest. */

    BlendSourceOp = source;
    BlendDestOp   = dest;

    GLuint SourceEnum = EpsilonGL_EnumBlendOp (source);
    GLuint DestEnum   = EpsilonGL_EnumBlendOp (dest);

    glBlendFunc (SourceEnum, DestEnum);
  }
}

void
e3dRenderState::SetColorMask (e3dRenderStates::Boolean red,
                              e3dRenderStates::Boolean green,
                              e3dRenderStates::Boolean blue,
                              e3dRenderStates::Boolean alpha)
{
  if ( ColorMaskR != red   ||
       ColorMaskG != green ||
       ColorMaskB != blue  ||
       ColorMaskA != alpha ) {
    const GLboolean r_mask = (red   == True) ? GL_TRUE : GL_FALSE;
    const GLboolean g_mask = (green == True) ? GL_TRUE : GL_FALSE;
    const GLboolean b_mask = (blue  == True) ? GL_TRUE : GL_FALSE;
    const GLboolean a_mask = (alpha == True) ? GL_TRUE : GL_FALSE;

    glColorMask (r_mask, g_mask, b_mask, a_mask);

    ColorMaskR = red;
    ColorMaskG = green;
    ColorMaskB = blue;
    ColorMaskA = alpha;
  }
}

void
e3dRenderState::SetCullFaces (e3dRenderStates::Boolean cull)
{
  if (CullFaces != cull) {
    if (cull) {
      glEnable (GL_CULL_FACE);
    } else {
      glDisable (GL_CULL_FACE);
    }

    CullFaces = cull;
  }
}

void
e3dRenderState::SetCullSide (Side side)
{
  if (CullSide != side) {
    if (side == Front) {
      glCullFace (GL_FRONT);
    } else {
      glCullFace (GL_BACK);
    }

    CullSide = side;
  }
}

void
e3dRenderState::SetFrontFace (Winding face)
{
  if (FrontFace != face)
  {
    if (face == Clockwise) {
      glFrontFace (GL_CW);
    } else {
      glFrontFace (GL_CCW);
    }

    FrontFace = face;
  }
}

void
e3dRenderState::SetPerspective (HintMode mode)
{
  if (PerspectiveCorrection != mode)
  {
    int EnumVal;

    switch (mode)
    {
      case DontCare:
        EnumVal = GL_DONT_CARE;
        break;
      case Fastest:
        EnumVal = GL_FASTEST;
        break;
      case Nicest:
        default:
        EnumVal = GL_NICEST;
        break;
    }
    
    glHint (GL_PERSPECTIVE_CORRECTION_HINT, EnumVal);
    
    PerspectiveCorrection = mode;
  }
}

void
e3dRenderState::SetFillMode (FillMode mode)
{
  if (Fill != mode)
  {
    int EnumVal;

    switch (mode)
    {
      case e3dRenderStates::Point: 
        EnumVal = GL_POINT;
        break;
      case e3dRenderStates::Wireframe:
        EnumVal = GL_LINE;
        break;
      case e3dRenderStates::Solid:
      default:
        EnumVal = GL_FILL;
        break;
    }
    
    glPolygonMode (GL_FRONT_AND_BACK, EnumVal);
    
    Fill = mode;
  }
}


void
e3dRenderState::SetClearColor (float r, float g, float b, float a)
{
  if (ClearColor [0] != r ||
      ClearColor [1] != g ||
      ClearColor [2] != b ||
      ClearColor [3] != a) {
    glClearColor (r, g, b, a);

    ClearColor [0] = r;
    ClearColor [1] = g;
    ClearColor [2] = b;
    ClearColor [3] = a;
  }
}

void
e3dRenderState::SetClearDepth (float depth)
{
  if (ClearDepth != depth) {
    glClearDepth (depth);

    ClearDepth = depth;
  }
}

void
e3dRenderState::SetClearStencil (int stencil)
{
  if (ClearStencil != stencil) {
    glClearStencil (stencil);

    ClearStencil = stencil;
  }
}

void
e3dRenderState::SetShadeModel (e3dRenderStates::ShadingModel model)
{
  if (ShadingModel != model) {
    switch (model) {
      case Smooth:
        glShadeModel (GL_SMOOTH);
        break;
      case Flat:
      default:
        glShadeModel (GL_FLAT);
        break;
    }

    ShadingModel = model;
  }
}

void
e3dRenderState::SetDepthTest (e3dRenderStates::Boolean test)
{
  if (DepthTest != test) {
    if (test == True) {
      glEnable (GL_DEPTH_TEST);
    } else {
      glDisable (GL_DEPTH_TEST);
    }

    DepthTest = test;
  }
}
void
e3dRenderState::SetDepthFunc (TestFunction func)
{
  if (DepthFunction != func) {
    switch (func) {
      case Less:
        glDepthFunc (GL_LESS);
        break;
      case LessOrEqual:
        glDepthFunc (GL_LEQUAL);
        break;
      case Greater:
        glDepthFunc (GL_EQUAL);
        break;
      case GreaterOrEqual:
        glDepthFunc (GL_GEQUAL);
        break;
      case Equal:
        glDepthFunc (GL_EQUAL);
        break;
      case NotEqual:
        glDepthFunc (GL_NOTEQUAL);
        break;
      case Always:
        glDepthFunc (GL_ALWAYS);
        break;
      case Never:
      default:
        glDepthFunc (GL_NEVER);
        break;
    }

    DepthFunction = func;
  }
}

void
e3dRenderState::SetDepthMask (e3dRenderStates::Boolean write)
{
  if (DepthWrite != write) {
    if (write == True) {
      glDepthMask (GL_TRUE);
    } else {
      glDepthMask (GL_FALSE);
    }

    DepthWrite = write;
  }
}

void
e3dRenderState::SetMatrix (e3dRenderStates::MatrixType type, float* matrix)
{
  int EnumVal;

  switch (type) {
    case Projection:
      EnumVal = GL_PROJECTION_MATRIX;
      memcpy (ProjectionMatrix, matrix, sizeof (float) * 16);
      break;
    case ModelView:
      EnumVal = GL_MODELVIEW_MATRIX;
      memcpy (ModelViewMatrix, matrix, sizeof (float) * 16);
      break;
    case Texture:
      EnumVal = GL_TEXTURE_MATRIX;
      memcpy (TextureMatrix, matrix, sizeof (float) * 16);
      break;
    default:
      /* ERROR!!! */
      return;
  }

//glPushAttrib (GL_TRANSFORM_BIT);
    glMatrixMode  (EnumVal);
    glLoadMatrixf (matrix);
//glPopAttrib ();
}

void
e3dRenderState::Clear (int buffers)
{
  int clear_mask = 0;

  if (buffers & ColorBuffer)
    clear_mask |= GL_COLOR_BUFFER_BIT;

  if (buffers & AccumBuffer)
    clear_mask |= GL_ACCUM_BUFFER_BIT;

  if (buffers & DepthBuffer)
    clear_mask |= GL_DEPTH_BUFFER_BIT;

  if (buffers & StencilBuffer)
    clear_mask |= GL_STENCIL_BUFFER_BIT;

  glClear (clear_mask);
}

float*
e3dRenderState::GetClearColor (void)
{
  return ClearColor;
}

float
e3dRenderState::GetClearDepth (void)
{
  return ClearDepth;
}

int
e3dRenderState::GetClearStencil (void)
{
  return ClearStencil;
}

float*
e3dRenderState::GetMatrix (e3dRenderStates::MatrixType type)
{
  switch (type) {
    case Projection:
      return ProjectionMatrix;
    case ModelView:
      return ModelViewMatrix;
    case Texture:
      return TextureMatrix;
    default:
      /* ERROR!!! */
      return NULL;
  }
}

e3dDriver*
Epsilon3D::CreateDriver (e3dRenderAPI* api, e3dRenderDevice* dev)
{
  return NULL;
}
